package com.zhxy.util.message.jsonpack;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.zhxy.util.message.Command;
import com.zhxy.util.message.KeepAlive;
import com.zhxy.util.message.MessageCodec;
import com.zhxy.util.message.MessageCodecException;
import com.zhxy.util.message.Request;
import com.zhxy.util.message.Response;

public class JsonpackCodec implements MessageCodec {
	
	public static final int MAGIC_NUMBER = 438284724;

	public static final int HEADER_SIZE = 12;

	public static final int MAX_BODY_SIZE = 256;
	
	public static final int MAX_PACKET_SIZE = HEADER_SIZE + MAX_BODY_SIZE;
	
	public static final int VERSION = 1;
	
	public static final String KEEPALIVE = "keepalive";
	
	public static final String COMMAND = "command";
	
	public static final String REQUEST = "request";
	
	public static final String RESPONSE = "response";
	
	private Gson gson = new Gson();
	
	private Map<String, Class<?>> registedType = new ConcurrentHashMap<String, Class<?>>();
	
	private Map<Class<?>, String> registedType2 = new ConcurrentHashMap<Class<?>, String>();

	public void registerType(String alias, Class<?> clazz) {
		registedType.put(alias, clazz);
		registedType2.put(clazz, alias);
	}
	
	@Override
	public ByteBuffer encode(Object message) {
		
		try {
			
			JsonElement root = gson.toJsonTree(message);
			Class<?> clazz = null;
			if (message instanceof KeepAlive) {
				root.getAsJsonObject().addProperty("type", KEEPALIVE);
			} else if (message instanceof Command) {
				root.getAsJsonObject().addProperty("type", COMMAND);
				clazz = ((Command)message).getMessage().getClass();
			} else if (message instanceof Request) {
				root.getAsJsonObject().addProperty("type", REQUEST);
				clazz = ((Request)message).getMessage().getClass();
			} else if (message instanceof Response) {
				root.getAsJsonObject().addProperty("type", RESPONSE);
				clazz = ((Response)message).getMessage().getClass();
			} else {
				throw new MessageCodecException("message必须是Command,Request,Response,KeepAlive");
			}
			
			if (clazz != null) {
				String alias = registedType2.get(clazz);
				if (alias != null) {
					root.getAsJsonObject().addProperty("name", alias);
				}
			}
			
			byte[] data = gson.toJson(root).getBytes("utf-8");
			
			return toPacket(data);
		} catch (IOException e) {
			throw new MessageCodecException(e);
		}
	}

	@Override
	public Object decode(ByteBuffer buf) {
		try {
			
			byte[] data = fromPacket(buf);
			if (data == null)
				return null;
			
			String json = new String(data, "utf-8");
			JsonObject o = (JsonObject)new JsonParser().parse(json);
			
			String type = o.has("type") ? o.get("type").getAsString() : null;
			String alias = o.has("name") ? o.get("name").getAsString() : null;
			JsonElement content = null;
			

			if (COMMAND.equals(type)) {
				content = o.get("message");
			} else if (REQUEST.equals(type)) {
				content = o.get("message");
			} else if (RESPONSE.equals(type)) {
				content = o.get("message");
			} else if (KEEPALIVE.equals(type)) {
				return new KeepAlive();
			} else {
				content = o;
			}
			
			Object msg = null;
			if (content.isJsonPrimitive()) {
				JsonPrimitive primitive = content.getAsJsonPrimitive();
				if (primitive.isBoolean()) {
					msg = primitive.getAsBoolean();
				} else if (primitive.isString()) {
					msg = primitive.getAsString();
				} else if (primitive.isNumber()) {
					msg = primitive.getAsNumber();
				}
			} else {
				Class<?> clazz = registedType.get(alias);
				if (clazz == null)
					throw new MessageCodecException("找不到对应的类型:" + type);
				msg = gson.fromJson(content, clazz);
			}
			
			if (COMMAND.equals(type)) {
				return new Command(msg);
			} else if (REQUEST.equals(type)) {
				return new Request(msg);
			} else if (RESPONSE.equals(type)) {
				return new Response(msg);
			} else {
				return msg;
			}

		} catch (UnsupportedEncodingException e) {
			throw new MessageCodecException(e);
		}
	}
	
	private ByteBuffer toPacket(byte[] data) {
		ByteBuffer buff = ByteBuffer.allocate(data.length + HEADER_SIZE);
		buff.putInt(MAGIC_NUMBER);
		buff.putInt(VERSION);
		buff.putInt(data.length);
		buff.put(data);
		buff.rewind();
		return buff;
	}
	
	private byte[] fromPacket(ByteBuffer buf) {
		PacketHeader header = checkHeader(buf);
		if (header == null)
			return null;
		if (header.getBodySize() > buf.remaining()) {
			return null;
		}
		
		byte[] data = new byte[header.getBodySize()];
		buf.get(data);
		
		return data;
	}
	
	private PacketHeader checkHeader(ByteBuffer buff) {
		
		if (buff.remaining() < HEADER_SIZE)
			return null;
		
		int magicNumber = buff.getInt();
		int version = buff.getInt();
		int size = buff.getInt();
		
		if (magicNumber != MAGIC_NUMBER && version != VERSION) {
			throw new MessageCodecException("非法的头");
		}
		if (size > MAX_BODY_SIZE) {
			throw new MessageCodecException("消息内容不能超过" + MAX_BODY_SIZE);
		}
		
		PacketHeader header = new PacketHeader();
		header.setVersion(version);
		header.setBodySize(size);
		return header;
	}

	
	public class PacketHeader {
		
		private int version;
		
		private int bodySize;

		public int getVersion() {
			return version;
		}

		public void setVersion(int version) {
			this.version = version;
		}

		public int getBodySize() {
			return bodySize;
		}

		public void setBodySize(int bodySize) {
			this.bodySize = bodySize;
		}

	}
}
